#include "process_supervision.h"

/* Registers the process supervisor as a supervisor of supervised */
int32_t register_supervisor(PCB* supervisor, int32_t supervised_pid){
  if(supervisor == NULL)
    return FAILURE;
  PCB* supervised = get_process_by_pid(supervised_pid);
  if(supervised != NULL){
    list* supervisors_l = pcb_get_supervisors(supervised);
    //inserts supervisor as one of supervised's supervisors (and vice versa)
    if(linked_list_add(supervisors_l,supervisor) == SUCCESS){
      if(linked_list_add(pcb_get_supervised(supervisor),supervised) == OUT_OF_MEMORY){
	//undo the first step
	linked_list_delete(supervisors_l,supervisor, compare_pcb, NULL);
	return FAILURE;
      }
      return SUCCESS;
    }
  }
  return FAILURE;
}

/* goes through all processes supervised by supervisor and removes it from their supervisors */
int32_t unregister_supervisor(PCB* supervisor){
  if(supervisor == NULL)
    return FAILURE;
  list* supervised = pcb_get_supervised(supervisor);
  list current;
  PCB* pcb;
  
  for(current = *supervised; current != NULL; current = *supervised){
    pcb = (PCB*)current->data;
    if(linked_list_delete(pcb_get_supervisors(pcb),supervisor, compare_pcb, NULL) == FAILURE)
      //something went wrong, we stop
      return FAILURE;
    //we can now delete this link. Since we do that for each link, we always delete from the head of the list
    if(linked_list_delete_after_prev(supervised, NULL, current, NULL) == FAILURE)
      return FAILURE;
  }
  return (*supervised == NULL?SUCCESS:FAILURE); //no process should remain at the end

}

/* goes through all supervised's supervisors and unregisters it as one of their supervised process */
int32_t unregister_supervised(PCB* supervised){
  if(supervised == NULL)
    return FAILURE;
  list* supervisors = pcb_get_supervisors(supervised);
  list current;
  PCB* pcb;

  for(current = *supervisors; current != NULL; current = *supervisors){
    pcb = (PCB*)current->data;
    if(linked_list_delete(pcb_get_supervisors(pcb),supervised, compare_pcb, NULL) == FAILURE)
      //something went wrong, we stop
      return FAILURE;
    //we can now delete this link. Since we do that for each link, we always delete from the head of the list
    if(linked_list_delete_after_prev(supervisors, NULL, current, NULL) == FAILURE)
      return FAILURE;
  }
  return (*supervisors == NULL?SUCCESS:FAILURE); //no process should remain at the end

}

/* signal terminated's termination to its supervisors */
int32_t signal_termination(PCB* terminated, int32_t code) {
  if(terminated == NULL)
    return FAILURE;
  int32_t supervisor_pid;
  list* supervisors = pcb_get_supervisors(terminated);
  list current;
  PCB* supervisor;

  /* signal all supervisors processes about termination */
  for(current = *supervisors; current != NULL; current = current->next){
    supervisor = (PCB*)current->data;
    supervisor_pid = pcb_get_pid(supervisor);
    /* send the message */
    message* m = malloc(sizeof(message), supervisor_pid);
    if(m == NULL)
      return OUT_OF_MEMORY;
    //build message with an integer data can't fail
    build_message(terminated, supervisor_pid, 0, SUPERVISION_MESSAGE, PINFO_DATA, m);
    return send_pcb(supervisor, m);
  }

  //the supervisors have been notified, let's clean the place
  if(unregister_supervisor(terminated) == SUCCESS){ //we are no longer supervising anyone
    return unregister_supervised(terminated);//we no longer are supervised by anyone
  }
  return FAILURE;
}

/* Removes supervised_pid from the processes supervisor supervises. Return SUCCESS in case of success, FAILURE otherwise. */
int32_t stop_supervision(PCB* supervisor, int32_t supervised_pid){
  PCB* supervised = get_process_by_pid(supervised_pid);
  if(supervised != NULL){
    list* supervised_list = pcb_get_supervised(supervisor);
    //let's delete supervised from this list
    if(linked_list_delete(supervised_list, supervised, compare_pcb, NULL) == SUCCESS)
      //lets's delete supervisor from supervised's pcb
      return linked_list_delete(pcb_get_supervisors(supervised), supervisor, compare_pcb, NULL);
    return FAILURE;
  }
  return FAILURE;
}


